/**
 * Copyright (c) 2010
 * Christian Ress <bart@oryx-uml.the-bart.org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

/**
 * @namespace Oryx name space for plugins
 * @name ORYX.Plugins
*/
 if(!ORYX.Plugins)
	ORYX.Plugins = new Object();

/**
 * The UMLX plugin provides layout methods referring to the UMLX stencilset. 
 * 
 * @class ORYX.Plugins.UMLX
 * @extends Clazz
 * @param {Object} facade The facade of the editor
 */
ORYX.Plugins.UMLX = 
/** @lends ORYX.Plugins.UMLX.prototype */
{
	/**
	 * Creates a new instance of the UMLX plugin and registers it on the
	 * layout events listed in the UMLX stencil set.
	 * 
	 * @constructor
	 * @param {Object} facade The facade of the editor
	 */
	construct: function(facade) {
		this.facade = facade;
		this.facade.registerOnEvent(ORYX.CONFIG.EVENT_PROPWINDOW_PROP_CHANGED, this.handlePropertyChanged.bind(this));
		this.facade.registerOnEvent('layout.umlx.class', this.handleLayoutClass.bind(this));
		this.facade.registerOnEvent('layout.umlx.attribute', this.handleLayoutAttribute.bind(this));
		this.facade.registerOnEvent('layout.umlx.association', this.handleLayoutAssociation.bind(this));
		this.facade.registerOnEvent('layout.umlx.enumeration', this.handleLayoutEnumeration.bind(this));

	},
	
	/**
	 * Add reading direction on load.
	 *
	 * Because the reading direction arrow is added to the
	 * label of an edge, we have to iterate over all edges
	 * and add the arrow on load for it to appear.
	 *
	 * @param {Object} event
	 */
	addReadingDirectionOnLoad : function(event) {
		this.facade.getCanvas().edges.each(function(shape){
			if (shape.properties["oryx-direction"] == "left" || shape.properties["oryx-direction"] == "right") {
				this.addReadingDirection(shape);
			}
		}.bind(this));
	},
	
	/**
	 * calculates the height of a text, taking line breaks into consideration
	 *
	 * @param {Object} labelElement the label
	 * @param {String} the label test
	 */
	
	calculateLabelHeight : function (labelElement, labelValue) {
		var fontSize = labelElement.getFontSize();
		var newlineOccurences = 1;
		
		labelValue.scan('\n', function() { newlineOccurences += 1; });
		
		// 0.75 account for padding around the label
		return newlineOccurences * fontSize + 0.75;
	},
	
	/**
	 * Add Reading Direction to the name label after it has been changed
	 */
	handlePropertyChanged : function(event) {
		if (event["key"] == "oryx-name") {
			this.addReadingDirection(event.elements[0]);
		}
	},
	
	/**
	 * count operations
	 */
	countOperations : function(shape) {
		fc = 0;
		for(i=0; i< shape.getChildNodes().length; i++) {
			if(shape.getChildNodes()[i].toString().indexOf('Operation')!=-1)
				fc++;
		}
		return fc;
	},
	
	/**
	 * count attributes
	 */
	countAttributes : function(shape) {
		ac = 0;
		for(i=0; i< shape.getChildNodes().length; i++) {
			if((shape.getChildNodes()[i].toString().indexOf('Attribute')!=-1)||(shape.getChildNodes()[i].toString().indexOf('Enumeration-Property')!=-1))
				ac++;
		}
		return ac;
	},	
	
	/**
	 * placed attributes and functions in the class-shape
	 */
	placeClassChilds : function(shape, childHeight, classWidth) {
		var childNodeList = shape.getChildNodes();
		var sortListA = Array();
		var ca = 0;
		var sortListM = Array();
		var cm = 0;
		for(i=0; i< childNodeList.length; i++) {
			if((childNodeList[i].toString().indexOf('Attribute')!=-1)||(childNodeList[i].toString().indexOf('Enumeration-Property')!=-1)) {
				sortListA[ca] = childNodeList[i];
				ca++;
			}
			if(childNodeList[i].toString().indexOf('Operation')!=-1) {
				sortListM[cm] = childNodeList[i];
				cm++;
			}
		}
		for(i=0; i< sortListA.length-1; i++) {
			for(k=i+1;k<sortListA.length; k++){
				if(sortListA[i].bounds.a.y > sortListA[k].bounds.a.y) {
					var cache= sortListA[i];
					sortListA[i] = sortListA[k];
					sortListA[k] = cache;						
				}
			}				
		}
		for(i=0; i< sortListM.length-1; i++) {
			for(k=i+1;k<sortListM.length; k++){
				if(sortListM[i].bounds.a.y > sortListM[k].bounds.a.y) {
					var cache= sortListM[i];
					sortListM[i] = sortListM[k];
					sortListM[k] = cache;						
				}
			}				
		}
		for(i=0; i< sortListA.length; i++) {
			sortListA[i].bounds.a.x = 0;
			sortListA[i].bounds.a.y = 22 + i*childHeight;
			sortListA[i].bounds.b.x = classWidth;
			sortListA[i].bounds.b.y = 22 + i*childHeight + 20;
			sortListA[i].refresh();
			sortListA[i].getLabels()[1].x = classWidth/2;
			
		}
		var attributepadding = ca*childHeight + 24 + 5;
		for(i=0; i< sortListM.length; i++) {
			sortListM[i].bounds.a.y = attributepadding + i*childHeight;
			sortListM[i].bounds.b.y = attributepadding + i*childHeight + 20;
			sortListM[i].refresh();
			
		}
	},
	
	/**
	 * Layout class shapes.
	 *   - make text italic when abstract
	 *   - resize methods and attributes boxes according to their content
	 */
	handleLayoutClass : function(event) {
		var shape = event.shape;
		var classWidth = shape.bounds.b.x - shape.bounds.a.x;
		
		if (shape.propertiesChanged["oryx-abstract"] == true) {
			var className = event.shape.getLabels().find(
					function(label) { return label.id == (event.shape.id + "className") }
				);
			
			if (shape.properties["oryx-abstract"] == true) {
				className.node.setAttribute("font-style", "italic");
			} else {
				className.node.setAttribute("font-style", "normal");
			}
		}
		
		
		if(shape.getChildNodes().length > 0) {
			var childHeight = 22;
			operationNumber = this.countOperations(shape);
			attributeNumber= this.countAttributes(shape);
			
			var attributes = event.shape.getLabels().find(
					function(label) { return label.id == (event.shape.id + "attributes") }
				);
			var methods = event.shape.getLabels().find(
					function(label) { return label.id == (event.shape.id + "methods") }
				);
			var separator = event.shape._svgShapes.find(
					function(element) { return element.element.id == (event.shape.id + "separator") }
				).element;
			var attributesHeight = attributeNumber*childHeight +5;
			var methodsHeight = operationNumber*childHeight + 5;
			// 24px account for the class name
			var distanceTilSeparator = 24 + attributesHeight;
			var distanceTilBottom = distanceTilSeparator + methodsHeight + 2;
			
			separator.setAttribute("y1", distanceTilSeparator);
			separator.setAttribute("y2", distanceTilSeparator);
			
			// realign methods label (so that oryx' internal references are correct)
			methods.y = distanceTilSeparator + 3;
			methods.node.setAttribute("y", distanceTilSeparator + 3);
			// realign the methods line by line (required for a visual change)
			for (var i = 0; i < methods.node.childElementCount; i++) {
				methods.node.childNodes[i].setAttribute("y", distanceTilSeparator + 2);
			}
			
			// resize shape
			shape.bounds.set(
				shape.bounds.a.x, 
				shape.bounds.a.y, 
				shape.bounds.b.x, 
				shape.bounds.a.y + distanceTilBottom + 5
			);
			this.placeClassChilds(shape, childHeight, classWidth);
			
		}		
			
	},
	
	/**
	 * count literals
	 */
	countLiterals : function(shape) {
		lc = 0;
		for(i=0; i< shape.getChildNodes().length; i++) {
			if(shape.getChildNodes()[i].toString().indexOf('Enumeration-Literal')!=-1)
				lc++;
		}
		return lc;
	},
	
	/**
	 * placed attributes and functions in the class-shape
	 */
	placeLiterals : function(shape, childHeight, enumWidth) {
		var literals = shape.getChildNodes();
		var sortList = Array();
		var lc = 0;
		
		for(i=0; i< literals.length; i++) {
			if(literals[i].toString().indexOf('Enumeration-Literal')!=-1) {
				sortList[lc] = literals[i];
				lc++;
			}			
		}
		for(i=0; i< sortList.length-1; i++) {
			for(k=i+1;k<sortList.length; k++){
				if(sortList[i].bounds.a.y > sortList[k].bounds.a.y) {
					var cache= sortList[i];
					sortList[i] = sortList[k];
					sortList[k] = cache;						
				}
			}				
		}
		
		for(i=0; i< sortList.length; i++) {
			sortList[i].bounds.a.x = 0;
			sortList[i].bounds.a.y = 32 + i*childHeight;
			sortList[i].bounds.b.x = enumWidth;
			sortList[i].bounds.b.y = 32 + i*childHeight + 20;
			sortList[i].refresh();			
		}		
	},
	
	/**
	 * Layout enumeration shapes.
	 *   
	 */
	handleLayoutEnumeration : function(event) {
		var shape = event.shape;
		var childHeight = 15;
		var enumWidth = shape.bounds.b.x - shape.bounds.a.x;
		
		if(shape.getChildNodes().length > 0) {
			literalNumber = this.countLiterals(shape);
			var labelHeight = 32;
			var literalHeight = literalNumber*childHeight;			
			// resize shape
			shape.bounds.set(
				shape.bounds.a.x, 
				shape.bounds.a.y, 
				shape.bounds.b.x, 
				shape.bounds.a.y + labelHeight + literalHeight + 5
			);
			
			this.placeLiterals(shape, childHeight, enumWidth);
		}
	},
	
	/**
	 * Layout the attribute shape. Resize according to their parent.
	 */
	handleLayoutAttribute : function(event) {
		var shape = event.shape;
		
	},
	
	
	/**
	 * Layout the interface and enumeration shape. Resize according to their content.
	 */
	handleLayoutList : function(event) {
		var shape = event.shape;
		
		if (shape.propertiesChanged["oryx-items"] == true) {
			var itemsValue = shape.properties["oryx-items"];
			var items = shape.getLabels().find(
					function(label) { return label.id == (event.shape.id + "items") }
				);
			
			var itemsHeight = this.calculateLabelHeight(items, itemsValue);
		
			var distanceTilBottom = 32 + itemsHeight + 2;
			
			// resize shape
			shape.bounds.set(
				shape.bounds.a.x, 
				shape.bounds.a.y, 
				shape.bounds.b.x, 
				shape.bounds.a.y + distanceTilBottom + 5
			);
		}
	},
	
	/**
	 * Draws the reading direction arrow when an association is changed.
	 */
	handleLayoutAssociation : function(event) {
		this.addReadingDirection(event.shape);
	},
	
	/**
	 * Adds the reading direction to the "name" label of an association.
	 */ 
	addReadingDirection : function(shape) {
		var name = shape.getLabels().find(
					function(label) { return label.id == (shape.id + "name") }
				);
		
		if (shape.properties["oryx-direction"] == "left") {
			name.text("◀ " + shape.properties["oryx-name"]);
		}
		else if (shape.properties["oryx-direction"] == "right") {
			name.text(shape.properties["oryx-name"] + " ▶");
		}
		else {
			name.text(shape.properties["oryx-name"]);
		}
		
		name.update();
	},
	
};

ORYX.Plugins.UMLX = Clazz.extend(ORYX.Plugins.UMLX);
